import { fork } from 'child_process';
import { ClientSide } from '../ipc.js';
import Path from 'path';
import { fileURLToPath } from 'url';
import { touch } from '../fs.js';

const __dirname = Path.dirname(fileURLToPath(import.meta.url));

export class ScriptManager {
    constructor({ sockPath, daemonScript, daemonEnv, logPath, errorPath }) {
        this._sock = sockPath;
        this._daemonEnv = daemonEnv;
        this._daemonScript = daemonScript;
        this._logPath = logPath;
        this._errorPath = errorPath;
        this._map = new Map();
    }

    register(name, path) {
        this._map.set(name, { path });
        return this;
    }

    async startScript(name, env) {
        if (!this._map.has(name)) {
            throw Error(`script not register`);
        }
        let connect = await this.getIpcClient();
        let { data } = await connect.publish('start', {
            path: this._map.get(name).path, env
        });
        if (data.code != 0) {
            throw Error(data.msg);
        }
        return { pid: data.pid };
    }

    async stopScript(name, pid) {
        if (!this._map.has(name)) {
            throw Error(`script not register`);
        }
        let connect = await this.getIpcClient();
        let { data } = await connect.publish('stop', {
            path: this._map.get(name).path, pid
        });
        if (data.code != 0) {
            throw Error(data.msg);
        }
    }

    async restartScript(name, pid) {
        if (!this._map.has(name)) {
            throw Error(`script not register`);
        }
        let connect = await this.getIpcClient();
        let { data } = await connect.publish('restart', {
            path: this._map.get(name).path, pid
        });
        if (data.code != 0) {
            throw Error(data.msg);
        }
    }

    async getScriptStatus(name, pid) {
        if (!this._map.has(name)) {
            throw Error(`script not register`);
        }
        let connect = await this.getIpcClient();
        let { data } = await connect.publish('status', {
            path: this._map.get(name).path, pid
        });
        if (data.code != 0) {
            throw Error(data.msg);
        }
    }

    async getIpcClient() {
        try {
            return await new ClientSide(this._sock).connect();
        } catch (e) {
            await this._startupDaemon();
            return await new ClientSide(this._sock).connect();
        }
    }

    async _startupDaemon() {
        if (!this._logPath) {
            this._logPath = Path.resolve(process.cwd, './log/info.log');
        }
        if (!this._errorPath) {
            this._errorPath = Path.resolve(process.cwd, './log/error.log');
        }
        if (!this._daemonScript) {
            this._daemonScript = Path.resolve(__dirname, './script.js');
        }
        await touch(this._logPath);
        await touch(this._errorPath);
        return new Promise((resolve, reject) => {
            let child = fork(this._daemonScript, {
                env: this._daemonEnv || {},
                detached: true,
                stdio: ['ipc', fs.openSync(this._logPath, 'a'), fs.openSync(this._errorPath, 'a'), 'pipe']
            });
            child.on('error', e => reject(e));
            child.on('message', ({ type, code }) => {
                if (type === 'initialize') {
                    code === 0 ? resolve() : reject();
                }
            });
        });
    }
}